/**
 *	The NeuroCoSA Toolkit
 *	Copyright (C) 2003-6 Stuart Meikle.
 *
 *	This is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU Lesser General Public
 *	License as published by the Free Software Foundation; either
 *	version 2.1 of the License, or (at your option) any later version.
 *
 *	This library is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *	Lesser General Public License for more details.
 *
 * @author	Stuart Meikle
 * @version	2006-halloween(mk2)
 * @license	LGPL
 */
package org.stumeikle.NeuroCoSA;

/* Comments:
 * 20060122	stumeikle	Initial prototyping of long term and 
 * 				short term memory system. Following on from
 *				autocluster and initially intended to enhance
 *				synapse expectation time learning and so 
 *				reduce surprise. 
*/

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.lang.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
import javax.swing.event.*;
import org.stumeikle.NeuroCoSA.AutoCluster.NMeasurable;

/**
 * Hash-table-like class. STLTMemory means short-term /long term memory.
 * Throw in your measurements and read out the statistical centroid and variance.
 * Currently only supports 1 VALUE. Maybe can support multiples later
 */
public class	STLTMemory
{
    //is this a class or an interface?
    LinkedList<NMeasurable>		iInsidePoints;	//those inside
    LinkedList<NMeasurable>		iOutsidePoints;  //and outside the variance areas / sphere
    NMeasurable				iCentroid;	//the mode/ centre of the distribution
    long				iNumContained;
    long				iTotalNum;
    double				iFractionInside;
    double				iSize;		//variance radius
    boolean				iFixed;		//bit like transferred to long term memory
    
    
    public	STLTMemory()
    {
//        System.out.println("Surprise DEBUG3 - initialising STLT Memory");
    	iInsidePoints = new LinkedList<NMeasurable>();
    	iOutsidePoints= new LinkedList<NMeasurable>();
    	iNumContained = 0;
    	iTotalNum = 0;
    	iFractionInside = 0.9; // user can define this depending on accuracy needed
    	iSize= Double.MAX_VALUE;
    	iFixed= false;
    	iCentroid = null;
    }
    
    public 	void		addValue( NMeasurable v )
    {
    	//add the value and update the estimates
    	//if necessary fix the data	    
	if (iFixed==true)	return;	//FUTURE --> still generate surprise
	
	iTotalNum ++;
	
        //(1) determine if the point is inside or outside the central area
        //(1.1) if this is the first point, just set it as the centroid
        if (iCentroid==null)
        {
            iCentroid = v;
            
  //          System.out.println("Surprise DEBUG1. Value of iInsidePoints=" + iInsidePoints + " and v=" + v);
	    iInsidePoints.add(v);
//            System.out.println("Surprise DEBUG2");
            return;
        }
        
        double	dist_sq = v.distanceSquaredTo( iCentroid );
        
        if (dist_sq< (iSize*iSize))	//inside
        {
            iNumContained ++;
            iInsidePoints.add( v );
            
       	    //we need to update the centroid but as soon as we do , we 
            //no longer know which points are inside and which not
            //centroid updating should only use the contained points
       	    //WE COULD ASSUME SMALL CENTROID MOTION TO START WITH
       	    NMeasurable		total;
       	    total = v.createNewZero();

	    ListIterator	i = iInsidePoints.listIterator();
	    for(;i.hasNext();)
	    {
	        NMeasurable	n = (NMeasurable) i.next();
	        
	        total.add( n );
	    }
	    
	    total.divideByScalar( (double)iNumContained );
	    iCentroid = total;
        }else //outside
        {
            //fine. we dont need to update the centroid.
            iOutsidePoints.add(v);
        }
        
        //recalculate the fractions and update
        //calculate the fraction inside and outside and if necessary move the
        //boundary
        double		ratio_in2out = (double) iNumContained / (double) iTotalNum;
        
        if (ratio_in2out >= iFractionInside)
        {
	    //too many inside, reduce the variance, but only if it makes sense to do so
            double	reduced_ratio_in2out = (double)( iNumContained -1) / (double) iTotalNum;
            
            if (reduced_ratio_in2out >= iFractionInside)
            {
            	double		max_dist= 0,dist;
            	NMeasurable	furthest=null;
            	
  		//go through the contained poitns, find the farthest from the centroid and remove it
		//from the contained list. update variance. add it to the outside list
		ListIterator	i = iInsidePoints.listIterator();
		for(;i.hasNext();)
		{
		    NMeasurable		m = (NMeasurable) i.next();
		    
		    dist = m.distanceSquaredTo( iCentroid );
		    if (dist>max_dist)
		    {
		        max_dist = dist;
		        furthest = m;
		    }
		}
		
		if (furthest != null)
		{
		    iInsidePoints.remove( (Object) furthest );
		    iOutsidePoints.add( furthest );
		    iNumContained --;
		    iSize = Math.sqrt( max_dist );
		}
            }
        }else
        {
            //too many outside, increase the variance.
            //go through all the points and find the point which is not
            //in the inside list but which is closest to the centroid. 
            //add to the contained list and update variance.
 	    double	min_dist = Double.MAX_VALUE;
 	    double	dist;
 	    NMeasurable	closest = null;
 	    
 	    ListIterator	i = iOutsidePoints.listIterator();
	    for(;i.hasNext();)
	    {
		NMeasurable		m = (NMeasurable) i.next();
		    
		dist = m.distanceSquaredTo( iCentroid );
		if (dist<min_dist)
		{
		    min_dist= dist;
		    closest = m;
		}    
            }
            
            if (closest !=null)
            {
                iOutsidePoints.remove((Object) closest);
                iInsidePoints.add(closest);
                iNumContained++;
                dist = Math.sqrt( min_dist );
                if (dist > iSize)	iSize=dist;
                //protects against outside points which
	        //somehow come to lie inside variance
            }
        }

	//check now if the error drops to a satisfactory level
	double 	e = 1/Math.sqrt( (double)iNumContained);
	if (e < 0.3) //temp change - was 0.05
	{
	    iFixed = true;
  //          System.out.println("Surprise DEBUG4 - clearing data");
	    iInsidePoints.clear();
	    iOutsidePoints.clear();
	}
    }
    	
    public	double		calculateProbability( NMeasurable v )
    {
	//return a probability of a given value being observed.
	//if iFixed is not yet true then return 1.0 (ie no surprises yet)
	if (iFixed !=true)
		return 1.0;
		
	//else
	//now we know iFractionInside of results fall within a radius of 'size'
	//so can we calc the probabilty of v falling at its own radius
	//assumes a kind of gaussian
	double	r = v.distanceTo( iCentroid );
	
	if (r<=iSize)
		return iFractionInside;
	
	double  n = -Math.log(1 - iFractionInside); //log = log natural
	double	m = (r*n)/iSize;
	double  p = 1 - ( 1-Math.exp(-m) );	//probabilty of falling outside that radius
	
	return p;
    }
    
    public	double		calculateSurpriseFromValue( NMeasurable v )
    {
    	double 	p = calculateProbability(v);
    	double  s;
    	
	//FUTURE: this needs to be a la Shannons theory 

        s = 1.0 / (p + 0.001);
    	
	return s;
    }
    
    public	double		calculateSurpriseFromProbability( double p )
    {
    	double  s;
    	
        s = 1.0 / (p + 0.001);
    	
	//FUTURE: this needs to be a la Shannons theory 

	return s;
    }

    
    public	double		getSurpriseThreshold()
    {
    	return 1.0 / iFractionInside;
    }
    
    public	double		getFractionInside()
    {
    	return iFractionInside;
    }
    
    public	double		calculateProbabilityOfMiss()
    {
    	//this is related to the error in the distribution as a whole (bit like the
    	//connection strength)
    	//if there's a large error, chance of miss is high let's say
	double 	e = 1/Math.sqrt( (double)iNumContained);
	return e;
    }
    
    public	NMeasurable 	getCentroid( )
    {
    	return iCentroid;
    }
    
    public   	double		getVariance( )
    {
    	return iSize;
    }
}
	
	
	
